﻿#if NETSTANDARD2_0 || NETCOREAPP3_0
using Microsoft.Extensions.Logging;
#endif
using Longbow.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Longbow.Net
{
    /// <summary>
    /// ISocketClient 内部实现类
    /// </summary>
    internal sealed class InternalSocketClient : ISocketClient
    {
        private TcpClient _client;
        private ILogger _logger;
        private FileLoggerProvider _loggerProvider;
        private Action<byte[]> _receive;
        private AutoResetEvent _revSyncLocker = new AutoResetEvent(false);
        private readonly List<byte> _gBuffer = new List<byte>();
        private byte[] _gData = null;
        private readonly IPEndPoint local;

        /// <summary>
        /// 获得 本地节点
        /// </summary>
        public IPEndPoint LocalEndPoint { get; private set; }

        /// <summary>
        /// 获得 远程节点
        /// </summary>
        public IPEndPoint RemoteEndPoint { get; private set; }

        /// <summary>
        /// 获得 异常描述信息
        /// </summary>
        public Exception Exception { get; private set; }

        /// <summary>
        /// 获得 Socket 连接配置
        /// </summary>
        /// <value>The options.</value>
        public SocketConnectOptions Options { get; }

        /// <summary>
        /// 获得 SocketClient 状态
        /// </summary>
        public SocketClientStatus Status { get; private set; }

        #region Construct
        /// <summary>
        /// 默认构造函数
        /// </summary>
        /// <param name="host">本地IP</param>
        /// <param name="port">本地端口</param>
        /// <param name="configure">SocketConnectOptions 配置回调函数</param>
        public InternalSocketClient(string host, int port, Action<SocketConnectOptions> configure)
        {
            LocalEndPoint = new IPEndPoint(string.IsNullOrEmpty(host) ? IPAddress.Any : IPAddress.Parse(host), port);
            local = LocalEndPoint;
            Options = new SocketConnectOptions();
            configure?.Invoke(Options);
            CreateLogger(host, port);
        }

        /// <summary>
        /// 此构造函数由 ISocketServer 接收到 Client 连接请求时调用
        /// </summary>
        /// <param name="tcpClient">TcpClient 实例</param>
        /// <param name="configure">SocketConnectOptions 配置回调函数</param>
        public InternalSocketClient(TcpClient tcpClient, Action<SocketConnectOptions> configure)
        {
            _client = tcpClient;
            Options = new SocketConnectOptions();
            configure(Options);
            LocalEndPoint = (IPEndPoint)tcpClient.Client.LocalEndPoint;
            local = LocalEndPoint;
            RemoteEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint;
            CreateLogger(RemoteEndPoint.Address.ToString(), RemoteEndPoint.Port);
        }
        #endregion

        private string BuildLogFileName(string remoteAddress, int remotePort) => Path.Combine(Path.GetDirectoryName(Options.LogFileName), $"{Path.GetFileNameWithoutExtension(Options.LogFileName)}-{LocalEndPoint.Address}-{LocalEndPoint.Port}-{remoteAddress}-{remotePort}{Path.GetExtension(Options.LogFileName)}");

        private void CreateLogger(string host, int port)
        {
            _loggerProvider?.Dispose();
            _loggerProvider = new FileLoggerProvider(new FileLoggerOptions() { FileName = BuildLogFileName(host, port) });
            _logger = _loggerProvider.CreateLogger<InternalSocketClient>();
        }

        /// <summary>
        /// 打开连接方法
        /// </summary>
        /// <param name="host">主机IP地址</param>
        /// <param name="port">主机端口</param>
        /// <param name="receiveCallback">异步接收数据回调函数</param>
        /// <param name="optionConfigure">SocketConnectOptions 配置回调函数</param>
        /// <param name="cancellationToken">连接取消令牌</param>
        public async Task<bool> ConnectAsync(string host, int port, Action<ISocketClient, byte[]> receiveCallback = null, Action<SocketConnectOptions> optionConfigure = null, CancellationToken cancellationToken = default)
        {
            optionConfigure?.Invoke(Options);
            Exception = null;
            Status = SocketClientStatus.NotConnected;

            await Task.Factory.StartNew(() =>
            {
                try
                {
                    // 异步连接超时, 但是连接会成功建立
                    // 此时应该判断_client内部的Socket对象是否连接
                    var cts = new CancellationTokenSource(Options.ConnectTimeout);
                    _client = new TcpClient(local);
                    var task = _client.ConnectAsync(host, port);
                    task.Wait(CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token).Token);
                    cts.Dispose();

                    Status = SocketClientStatus.Success;
                    LocalEndPoint = (IPEndPoint)_client.Client.LocalEndPoint;
                    RemoteEndPoint = new IPEndPoint(IPAddress.Parse(host), port);

                    CreateLogger(host, port);
                    Log(LogLevel.Information, $"Connected {LocalEndPoint}-{host}:{port}");
                    ReadAsync(receiveCallback);
                }
                catch (OperationCanceledException ex1)
                {
                    Exception = ex1;
                    if (cancellationToken.IsCancellationRequested)
                    {
                        // Cancel
                        Status = SocketClientStatus.ConnectCancelled;
                        Log(LogLevel.Information, $"Connect {host}:{port} -- Cancelled");
                    }
                    else
                    {
                        // Timeout
                        Status = SocketClientStatus.ConnectTimedOut;
                        Log(LogLevel.Information, $"Connect {host}:{port} -- Timeout");
                    }

                    // 连接任务取消后 Socket 连接成功，调用此方法销毁连接
                    Close();
                }
                catch (Exception ex)
                {
                    while (ex.InnerException != null) ex = ex.InnerException;
                    Exception = ex;
                    Status = SocketClientStatus.SocketError;
                    Log(LogLevel.Information, $"Connect {host}:{port} -- Error{Environment.NewLine}Message: {Exception}{Environment.NewLine}");
                }
            }, TaskCreationOptions.LongRunning);
            return Status == SocketClientStatus.Success;
        }

        private static readonly object _multilpleThreadLocker = new object();
        private void DataCheck(byte[] data)
        {
            if (data == null) throw new ArgumentNullException(nameof(data));
            if (data.Length == 0) throw new InvalidOperationException("data length must more than one.");

            // 判断Socket状态
            if (_client == null || !_client.Connected)
            {
                Status = SocketClientStatus.NotConnected;
                throw new SocketException((int)SocketError.NotConnected);
            }
        }

        /// <summary>
        /// 同步发送数据方法
        /// </summary>
        /// <param name="data">要发送的数据 字节数组</param>
        /// <param name="optionConfigure">SocketSendOptions 配置回调函数</param>
        /// <param name="cancellationToken">CancellationToken 实例</param>
        /// <returns>同步发送后接收到的数据 字节数组</returns>
        public byte[] Send(byte[] data, Action<SocketSendOptions> optionConfigure = null, CancellationToken cancellationToken = default)
        {
            DataCheck(data);

            lock (_multilpleThreadLocker)
            {
                // 线程保护，多线程调用同步发送时，仅允许一个线程发送
                if (_receive != null) throw new SocketException((int)SocketError.InProgress);

                _receive = buffer =>
                {
                    _gData = buffer;
                    _revSyncLocker.Set();
                };
            }

            _revSyncLocker.Reset();
            optionConfigure?.Invoke(Options);
            if (!SendAsync(data, null, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult())
            {
                _receive = null;
                return new byte[0];
            }

            if (!_revSyncLocker.WaitOne(Options.ReceiveTimeout)) { Status = SocketClientStatus.ReceiveTimedOut; _gData = new byte[0]; }
            _receive = null;
            return _gData;
        }

        /// <summary>
        /// 发送数据到远程计算机，发送数据前请确保 socket 已经连接
        /// </summary>
        /// <param name="data">要发送的数据</param>
        /// <param name="optionConfigure">用于更新配置项</param>
        /// <param name="cancellationToken">取消发送Token</param>
        /// <returns></returns>
        public async Task<bool> SendAsync(byte[] data, Action<SocketSendAsyncOptions> optionConfigure = null, CancellationToken cancellationToken = default)
        {
            DataCheck(data);

            var ret = false;
            optionConfigure?.Invoke(Options);

            Exception = null;
            try
            {
                await _client.GetStream().WriteAsync(data, 0, data.Length, cancellationToken);
                Status = SocketClientStatus.Success;
                LogData(data, "Send");
                ret = true;
            }
            catch (TaskCanceledException ex)
            {
                Exception = ex;
                Status = SocketClientStatus.SendCancelled;
                LogData(data, "SendAsync Cancelled");
            }
            return ret;
        }

        internal void ReadAsync(Action<ISocketClient, byte[]> receiveCallback) => Task.Factory.StartNew(() =>
        {
            while (_client != null && _client.Connected)
            {
                var stream = _client.GetStream();
                var bReceive = new byte[_client.ReceiveBufferSize];
                try
                {
                    do
                    {
                        stream.ReadTimeout = Options.ReceiveTimeout;
                        var length = stream.Read(bReceive, 0, bReceive.Length);
                        if (length == 0)
                        {
                            Log(LogLevel.Information, $"Disconnect From {RemoteEndPoint}");
                            // Close connection
                            Close();

                            Status = SocketClientStatus.NotConnected;
                            _gBuffer.Clear();
                            receiveCallback?.Invoke(this, new byte[0]);
                            _receive?.Invoke(new byte[0]);
                            break;
                        }

                        var buffer = new byte[length];
                        Buffer.BlockCopy(bReceive, 0, buffer, 0, length);
                        _gBuffer.AddRange(buffer);
                        LogData(buffer, "Receive");
                        if (CheckEndData())
                        {
                            var data = _gBuffer.ToArray();
                            receiveCallback?.Invoke(this, data);
                            _receive?.Invoke(data);
                            _gBuffer.Clear();
                            break;
                        }
                    }
                    while (_client.Connected);
                }
                catch (Exception ex) when (ex.InnerException is SocketException ex1)
                {
                    Exception = ex1;
                    switch (ex1.SocketErrorCode)
                    {
                        case SocketError.TimedOut:
                            Status = SocketClientStatus.ReceiveTimedOut;
                            Log(LogLevel.Information, $"Receive from {RemoteEndPoint} -- {ex1.SocketErrorCode}");
                            break;
                        case SocketError.ConnectionReset:
                            // 远程Socket连接进程直接被杀掉
                            Status = SocketClientStatus.SocketError;
                            receiveCallback?.Invoke(this, new byte[0]);
                            _receive?.Invoke(new byte[0]);
                            Log(LogLevel.Information, $"ConnectionReset From {RemoteEndPoint}");
                            break;
                        default:
                            Status = SocketClientStatus.SocketError;
                            Log(LogLevel.Information, $"{ex1.SocketErrorCode} Exception: {ex.Message}{Environment.NewLine}{ex.StackTrace}");
                            break;
                    }
                }
            }
        }, TaskCreationOptions.LongRunning);

        private bool CheckEndData()
        {
            if (Options.EOF.Length == 0 && Options.DataLength == 0) return true;

            // 数据长度判断
            var count = _gBuffer.Count;
            if (Options.DataLength > 0) return count >= Options.DataLength;

            // 判断结束符包含模式，此处要考虑拆包
            if (Options.ContainerCheckModel) return Options.EOF.Any(b => _gBuffer.Contains(b));

            // 数据结束符长度
            var length = Options.EOF.Length;
            if (count < length) return false;

            // 比对结束符结束符
            var data = _gBuffer.Skip(count - length);
            return data.SequenceEqual(Options.EOF);
        }

        private void LogData(byte[] data, string operation)
        {
            if (data != null)
            {
                var message = Options.Hex ? BitConverter.ToString(data) : Options.Encoding.GetString(data);
                var sb = new StringBuilder();
                sb.AppendLine($"{DateTime.Now} -- {operation} Data");
                sb.Append(message);
                Log(LogLevel.Information, sb.ToString());
            }
        }

        private void Log(LogLevel logLevel, string msg)
        {
            if (Options.Enabled)
            {
                _logger.Log(logLevel, $"{DateTime.Now} -- {msg}");
            }
        }

        /// <summary>
        /// Close 方法 内部调用 Dispose 方法
        /// </summary>
        public void Close()
        {
            _client?.Close();
            _client = null;

            _loggerProvider?.Dispose();
            _loggerProvider = null;
        }

        #region Implementing Disposing
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                Close();
            }
        }

        /// <summary>
        /// Dispose 方法
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}
